
#include "../iface/debug.h"
#include "../iface/APRInterface.h"
#include "../utils/StringUtils.h"
#include "APRAutoLockRead.h"
#include "APRAutoLockWrite.h"
#include "APRBucketBrigade.h"

APRBucketBrigade::APRBucketBrigade(apr_pool_t *imp)
{
   apr_status_t status;
   status = apr_pool_create(&mp, imp);
   CheckAPRError(status);
   bucketalloc = apr_bucket_alloc_create(mp);
   bucketbrgde = apr_brigade_create(mp, bucketalloc);
   rwlock = new APRRWMutex(mp);
   csize = 0; // csize is amount in memory, not real length
}

APRBucketBrigade::~APRBucketBrigade()
{
   apr_status_t status;
   status = apr_brigade_destroy(bucketbrgde);
   CheckAPRError(status);
   apr_bucket_alloc_destroy(bucketalloc);
   delete rwlock;
   apr_pool_destroy(mp);
}

void APRBucketBrigade::AddSocketBucket(apr_socket_t *tsock)
{
   APRAutoLockWrite awlock(rwlock);
   apr_bucket *tbucket = apr_bucket_socket_create(tsock, bucketalloc);
   APR_BRIGADE_INSERT_TAIL(bucketbrgde, tbucket);
}

void APRBucketBrigade::WriteEOS()
{
   APRAutoLockWrite awlock(rwlock);
   apr_bucket *tbucket = apr_bucket_eos_create(bucketalloc);
   APR_BRIGADE_INSERT_TAIL(bucketbrgde, tbucket);
}

apr_size_t APRBucketBrigade::WriteFile(const char *filename, apr_size_t start, apr_size_t end, int isbinary)
{
   apr_status_t status;
   apr_size_t eend = end;
   apr_file_t *newFP;
   status = apr_file_open(&newFP, StringUtils::expandMyHomeDirectoryTilde(filename, mp), ((isbinary) ? APR_FOPEN_BINARY : 0)|APR_FOPEN_READ|APR_FOPEN_BUFFERED, APR_FPROT_OS_DEFAULT, mp);
   CheckAPRError(status);
   if (eend == 0)
   {
      apr_finfo_t finfo;
#ifdef USE_STAT_FILE_SIZE
      status = apr_file_info_get(&finfo, APR_FINFO_SIZE, newFP);
      CheckAPRError(status);
#else
      apr_off_t offset = 0;
      status = apr_file_seek(newFP, APR_END, &offset);
      CheckAPRError(status);
      finfo.size = offset;
      offset = 0;
      status = apr_file_seek(newFP, APR_SET, &offset);
      CheckAPRError(status);
#endif
      eend = (apr_size_t)finfo.size;
   }

   apr_bucket *fileb = apr_bucket_file_create(newFP, start, eend - start, mp, bucketalloc);
   if (fileb == NULL)
      errprint("bucket allocation failed");
   APRAutoLockWrite awlock(rwlock);
   APR_BRIGADE_INSERT_TAIL(bucketbrgde, fileb);
   csize += fileb->length;
   return (eend - start);
}

apr_size_t APRBucketBrigade::Writef(const char *format, ...)
{
   va_list args;
   va_start(args, format);
   char *actual = StringUtils::varprintf_va(format, args);
   va_end(args);
   apr_size_t result = Write(actual, strlen(actual));
   free(actual);
   return result;
}

apr_size_t APRBucketBrigade::Write(const char *data, apr_size_t numbytes)
{
   if (numbytes == 0) return 0;
   char *bbuf = (char *)apr_bucket_alloc(numbytes, bucketalloc);
   if (!bbuf) return 0;
   memcpy(bbuf, data, numbytes);
   APRAutoLockWrite awlock(rwlock);
   apr_bucket *tbucket = apr_bucket_heap_create(bbuf, numbytes, apr_bucket_free, bucketalloc);
   APR_BRIGADE_INSERT_TAIL(bucketbrgde, tbucket);
   csize += numbytes;
   return numbytes;
}

apr_size_t APRBucketBrigade::Read(char *data, apr_size_t numbytes)
{
   APRAutoLockRead arlock(rwlock);
   apr_status_t status;
   apr_size_t actual_length = numbytes;
   status = apr_brigade_flatten(bucketbrgde, data, &actual_length);
   CheckAPRError(status);
   return actual_length;
}

apr_size_t APRBucketBrigade::Delete(apr_size_t numbytes)
{
   APRAutoLockWrite awlock(rwlock);
   apr_size_t remaining = numbytes;
   apr_bucket *tbucket = APR_BRIGADE_FIRST(bucketbrgde);
   apr_bucket *tnext = NULL;
   while (tbucket != APR_BRIGADE_SENTINEL(bucketbrgde))
   {
      if (APR_BUCKET_IS_EOS(tbucket)) break;
      if (APR_BUCKET_IS_SOCKET(tbucket)) { remaining = 0; break; }
      tnext = APR_BUCKET_NEXT(tbucket);
      if (tbucket->length > remaining)
      {
         tbucket->start += remaining;
         tbucket->length -= remaining;
         csize -= remaining;
         remaining = 0;
         break;
      } else
      {
         remaining -= tbucket->length;
         csize -= tbucket->length;
#ifdef __WIN32__
#ifndef WINDOWS_IS_HOPELESSLY_RETARDED_FREE
#define WINDOWS_IS_HOPELESSLY_RETARDED_FREE free
#endif
#undef free
#endif
         apr_bucket_delete(tbucket);
#ifdef __WIN32__
#define free WINDOWS_IS_HOPELESSLY_RETARDED_FREE
#endif
      }
      tbucket = tnext;
   }
   return numbytes - remaining;
}

apr_size_t APRBucketBrigade::Pop(char *data, int numbytes)
{
   apr_size_t read = Read(data, numbytes);
   apr_size_t length = RealLength();
   apr_size_t pop= Delete(read);
   if (pop != read)
      errprint("Read and deleted data do not match, read %d bytes, deleted %d bytes (length reported to be %d).", read, pop, length);
   return pop;
}

int APRBucketBrigade::BufferToBucketBrigade(const char *buffer, size_t *length, size_t smallblock)
{
   for (int k = 0; k < ((int)(*length)); k += smallblock)
   {
      int actualamt = (((*length) - k) < smallblock) ? (*length) - k : smallblock;
      int bytes = Write(&(buffer[k]), actualamt);
      if (bytes == 0)
      {
         errprint("push buffer to bucket brigade failed - incomplete data (%d/%d bytes written)", k, (*length));
         if (length) *length = k;
         WriteEOS();
         return -1;
      }
      k -= (smallblock - bytes);
   }
   WriteEOS();
   return 0;
}

int APRBucketBrigade::BucketBrigadeToBuffer(char **buffer, size_t *length)
{
   apr_size_t reallen = RealLength();
   (*buffer) = (char *)malloc(sizeof(char)*(reallen+1));
   if (reallen > 0)
   {
      apr_size_t gotreal = Pop((*buffer), reallen);
      if (gotreal != reallen)
      {
         errprint("Could not get all of the bucket brigade (%" APR_SIZE_T_FMT "/%" APR_SIZE_T_FMT " bytes obtained)", gotreal, reallen);
         if (length) (*length) = gotreal;
         return -1;
      }
   }
   if (length) (*length) = reallen;
   return 0;
}
